#!/bin/bash
#
# Copyright 2020 Joyent, Inc.
#
# Output some info representing the system.  Default: JSON
#
# IMPORTANT:
#
#  - Don't use anything here that you don't include in the live image!
#
#  - This data gets cached in /tmp/.sysinfo.* and *DOES NOT* automatically
#    have the cache refreshed.  If you change something where the change
#    needs to be visible to sysinfo, make sure you run this with either
#    the -f or -u flag after changing the system's state.
#

#set -o xtrace

PATH=/system/usr/bin:/usr/bin:/sbin:/usr/sbin:/smartdc/bin:/opt/local/bin:/opt/local/sbin

CACHE_FILE_PARSABLE="/tmp/.sysinfo.parsable"
CACHE_FILE_JSON="/tmp/.sysinfo.json"
CACHE="true"
UNAME_S=$(uname -s)
ZONENAME=$(zonename)
FORCE=
PARSABLE=
UPDATE_ONLY=
ZSTAT=

while getopts fnpu? name
do
    case $name in
    f)
        # Force re-running commands (don't read the cached values)
        FORCE="true"
        ;;
    p)
        # Output key=value format, not json
        PARSABLE="true"
        ;;
    u)
        # Update cache *only*, do not output.
        FORCE="true"
        UPDATE_ONLY="true"
        ;;
    ?)
        printf "Usage: %s: [-f] [-p] [-u]\n"  $0
        exit 2
    ;;
    esac
done

if [[ ${UNAME_S} != "SunOS" ]]; then
    echo "This program is for use in SmartOS only."
    exit 1
fi

if [[ ${EUID} != 0 ]]; then
    echo "This program can only be run as root."
    exit 1
fi

# If we're not forcing update and we have a cached version, use that!
if [[ -z ${FORCE} ]]; then
    if [[ -z ${PARSABLE} && -s ${CACHE_FILE_JSON} ]]; then
        exec cat ${CACHE_FILE_JSON}
        exit 3
    elif [[ -s ${CACHE_FILE_PARSABLE} ]]; then
        exec cat ${CACHE_FILE_PARSABLE}
        exit 3
    fi
fi

# configfile here is only used on headnode
configfile="$(svcprop -p 'joyentfs/usb_copy_path' svc:/system/filesystem/smartdc:default 2>/dev/null)/config"
if [[ ! -f ${configfile} ]]; then
    configfile="/mnt/$(svcprop -p 'joyentfs/usb_mountpoint' svc:/system/filesystem/smartdc:default 2>/dev/null)/config"
fi

# helper to set global "normalized" to the expanded version of MAC ($1)
function normalize_mac()
{
    local mac=$1
    if [ -z "${mac}" ]; then
        echo "FATAL: unable to normalize empty mac!"
        exit 1
    fi

    normalized=`echo ${mac} | awk -F':' '{ print $1, $2, $3, $4, $5, $6 }' | { read o1 o2 o3 o4 o5 o6 junk
        printf "%02x:%02x:%02x:%02x:%02x:%02x" 0x${o1} 0x${o2} 0x${o3} 0x${o4} 0x${o5} 0x${o6}
    }`

    # ensure results are 'normal'
    echo "${normalized}" | grep "[a-fA-F0-9]\{2\}\:[a-fA-F0-9]\{2\}\:[a-fA-F0-9]\{2\}\:[a-fA-F0-9]\{2\}\:[a-fA-F0-9]\{2\}\:[a-fA-F0-9]\{2\}" >/dev/null 2>&1
    if [ "$?" != "0" ]; then
        echo "FATAL: failed to normalize MAC '${mac}'"
        exit 1
    fi
}

function get_smbios_system_info()
{
    # This puts the variables we're pulling out into the local environment
    eval $(smbios -t SMB_TYPE_SYSTEM | tr -d "\"" \
        | egrep "Manufacturer: |Product: |Serial Number: |UUID \(Endian-corrected\): |UUID: |SKU Number: |Version: |Family: " \
        | sed -e 's/^ *//' \
        -e 's/: /="/' \
        -e 's/ *$/"/' \
        -e 's/Serial Number/Serial_Number/' \
        -e 's/SKU Number/SKU_Number/' \
        -e 's/Version/HW_Version/' \
        -e 's/Family/HW_Family/' \
        -e 's/UUID (Endian-corrected)/Fixed_UUID/')

    vendor=$(smbios -t SMB_TYPE_BIOS | grep "Vendor: " \
        | sed -e 's/^.*Vendor: //;s/ *$//')

    # If we are a bhyve guest, we can use the correct UUID format; otherwise we
    # use the network-endian one for compatibility reasons.
    if [ "$vendor" = "BHYVE" -a -n "$Fixed_UUID" ]; then
        UUID=$Fixed_UUID
    fi

    # overwrite UUID if config dictates otherwise
    tmp_uuid=$(/usr/bin/bootparams | grep "^override_uuid=" | cut -f2 -d'=')
    if [[ -n $tmp_uuid ]]; then
        UUID=$tmp_uuid
    fi

    #echo "${UUID}"
    #echo "${Product}"
    #echo "${Serial_Number}"
    #echo "${Manufacturer}"
}

function get_nonglobal_info()
{
    UUID=$(/usr/sbin/mdata-get sdc:uuid)
    if [[ $? != 0 ]]; then
        UUID=${ZONENAME}
    fi
    QUOTA=$(/usr/sbin/mdata-get sdc:quota)
    if [[ $? != 0 ]]; then
        QUOTA="unknown"
        if [[ $(/usr/sbin/zfs list -H | wc -l | tr -d ' ') != 0 ]]; then
            ROOTQUOTA=$(($(/usr/sbin/zfs get -pHo value quota / | tr -d 'GMT') / (1024 * 1024 * 1024)))
            if [[ $? == 0 ]]; then
                QUOTA="${ROOTQUOTA}"
            fi
        else
            QUOTA=$(($(df -k / | tail -1 | awk '{ print $2 }') / (1024 * 1024)))
        fi
    fi
    if [[ ${QUOTA} == "0" ]]; then
        QUOTA="none"
    elif [[ ${QUOTA} != "unknown" ]]; then
        QUOTA="${QUOTA}G"
    fi
}

function get_memory_mib()
{
    # Get (misnamed in prtconf) memory size in Mebibytes
    Memory_in_MiB=`prtconf 2>/dev/null \
            | grep "Memory size: [0-9]* Megabytes" \
            | cut -d' ' -f3`

    #echo "${Memory_in_MiB}"
}

function get_smartos_cpu_info()
{
    if [[ ${ZONENAME} == "global" ]]; then
        CPU_Version=`smbios -t SMB_TYPE_PROCESSOR | grep -v "Version: 0000000000" \
            | grep "Version: " \
            | head -n1 \
            | tr -s ' ' \
            | sed -e 's/^ *Version: //' \
            | sed -e 's/ *$//'`
        if [[ -z ${CPU_Version} ]]; then
            CPU_Version="Unknown"
        fi

        isainfo=$(isainfo -x)
        if [[ -n $(echo " ${isainfo} " | /usr/bin/grep " vmx ") ]]; then
            CPU_Virtualization="vmx"
        elif [[ -n $(echo " ${isainfo} " | /usr/bin/grep " amd_svm ") ]]; then
            CPU_Virtualization="svm"
        else
            CPU_Virtualization="none"
        fi
    fi

    CPU_Socket_Count=$(psrinfo -t -p)
    CPU_Core_Count=$(psrinfo -t -c)
    CPU_Count=$(psrinfo -t)
    CPU_Online_Count=$(( $(psrinfo -t -S "on-line") + $(psrinfo -t -S "no-intr") ))

    #
    # Below, we also report these two legacy values, both of which are
    # unfortunately named:
    #
    # CPU Physical Cores: this is really $CPU_Socket_Count
    # CPU Total Cores: this is really $CPU_Count
    #
    # Consumers that care about the CPU resources actually available to use
    # should look to $CPU_Online_Count.
    #
}

function get_bhyve_capability()
{
    Bhyve_Capable="false"
    Bhyve_Max_Vcpus="0"
    HVM_API="false"

    if [[ -x /usr/lib/brand/bhyve/bhhwcompat ]]; then
        if /usr/lib/brand/bhyve/bhhwcompat; then
            Bhyve_Capable="true"
            vcpus=$(/usr/lib/brand/bhyve/bhhwcompat -c | /usr/bin/grep "^[0-9]*$")
            if [[ -n ${vcpus} ]]; then
                Bhyve_Max_Vcpus=${vcpus}
            fi
            HVM_API="true"
        fi
    fi
}

function get_live_image_buildstamp()
{
    # Add joyent buildstamp to SYSTEM_INFO
    Live_Image=$(uname -v | sed -e "s/.*_//")

    #echo "${Live_Image}"
}

function get_system_type()
{
    Uname_System=${UNAME_S}
}

function get_vm_capable()
{
    # XXX all compute nodes should now support VMs
    VM_Capable="true"
}

function get_hostname()
{
    Hostname=$(hostname)
}

function get_zpool_disks()
{
    local zpool=$1
    local disks=$(/usr/bin/disklist -n)
    ZSTAT=$(/usr/sbin/zpool status ${zpool} | awk '/[a-z]/{ print $1 }')
    Zpool_disks=

    for disk in ${disks}; do
        if [[ "${ZSTAT}" =~ "${disk}" ]]; then
            Zpool_disks="${Zpool_disks},${disk}"
        fi
    done

    Zpool_disks=${Zpool_disks/#,/}
}

function get_zpool_profile()
{
    local zpool=$1
    local profiles=( mirror raidz3 raidz2 raidz )
    Zpool_profile="striped"

    for profile in ${profiles[*]}; do
        if [[ "${ZSTAT}" =~ "${profile}" ]]; then
            Zpool_profile=${profile}
            break
        fi
    done
}

function get_zpool()
{
    if [[ $(zpool list) != "no pools available" ]]; then
        Zpool=$(svcprop -p "config/zpool" svc:/system/smartdc/init);

        local used=$(zfs get -Hp -o value used ${Zpool})
        local available=$(zfs get -Hp -o value available ${Zpool})
        local size=$(( $used + $available ))
        Zpool_size=$(($size / 1024 / 1024 / 1024))
        Zpool_creation=$(zfs get -Hpo value creation ${Zpool})
        Zpool_encrypted=$(zfs get -Hpo value encryption ${Zpool})

        # The encryption property can be 'off', 'on', or a specific mechanism
        # We treat anything but 'off' as enabled.
        if [[ "$Zpool_encrypted" == "off" ]]; then
            Zpool_encrypted="false"
        else
            Zpool_encrypted="true"
        fi

        get_zpool_disks ${Zpool}
        get_zpool_profile ${Zpool}
    fi
}

function get_disks()
{
    ORIGIFS=$IFS

    # set $IFS to end-of-line
    IFS=`echo -en "\n\b"`

    count=1
    for line in $(/usr/bin/disklist -s 2>/dev/null); do
        Disks[${count}]=${line}
        ((count++))
    done

    # set $IFS back
    IFS=$ORIGIFS
}

# grep through the appropriate networking config source for a parameter:
# - boot-time file (used exclusively if present)
# - bootparams
# - usbkey config
function net_conf_grep()
{
    if /usr/lib/sdc/net-boot-config --enabled; then
        /usr/lib/sdc/net-boot-config | grep "$1"
    else
        /usr/bin/bootparams 2>/dev/null | grep "$1"
        grep "$1" ${configfile} 2>/dev/null
    fi
}

function get_aggregation_mappings()
{
    local aggr_name
    count=1
    for line in $(net_conf_grep "^[a-zA-Z0-9_]*_aggr="); do
        fields=(${line//=/ })
        key=${fields[0]}
        val=${fields[1]}

        aggr_name=$(echo ${key} | sed -e 's/_aggr$//')
        Aggrs[${count}]=$aggr_name
        macs=(${val//,/ })
        for mac in "${macs[@]}"; do
            normalize_mac ${mac}

            if [[ -z ${AggrMacs[${aggr_name}]} ]]; then
                AggrMacs[${aggr_name}]=${normalized}
            else
                AggrMacs[${aggr_name}]="${AggrMacs[${aggr_name}]},${normalized}"
            fi

            if [[ -z ${AggrInterfaceList[${count}]} ]]; then
                AggrInterfaceList[${count}]=${normalized}
            else
                AggrInterfaceList[${count}]="${AggrInterfaceList[${count}]},${normalized}"
            fi
        done
        ((count++))
    done
}

function is_aggr() {
    for aggr in "${Aggrs[@]}"; do
        if [[ "$aggr" == "$1" ]]; then
            return 0
        fi
    done
    return 1
}

function get_smartos_network_interfaces()
{
    count=1

    # ignore 'LINK SLOT ADDRESS INUSE CLIENT' line
    for line in $(/sbin/dladm show-phys -m \
        | grep -v "^LINK " | awk '{ print $1,$3 }' | tr ' ' '='); do

        nicnames=
        fields=(${line//=/ })
        iface=${fields[0]}
        normalize_mac ${fields[1]}
        mac=${normalized}
        ip4addr=$(ifconfig ${iface} 2>/dev/null \
            | grep "inet " | awk '{ print $2 }')

        nic_tag_count=0
        while [[ ${nic_tag_count} -lt ${NicTagCount} ]]; do
            tag_name=${NicTagNames[${nic_tag_count}]}
            tag_mac=${NicTagMacs[${nic_tag_count}]}
            ((nic_tag_count++))

            # Aggregations in this list will have a MAC address of "-",
            # so this won't match
            if [[ "${tag_mac}" == "${mac}" ]]; then
                [[ -n ${nicnames} ]] && nicnames="${nicnames},"
                nicnames="${nicnames}${tag_name}"
                NicTags[${nic_tag_count}]="${tag_name}=${iface}"
            fi
        done

        NetworkInterfaces[${count}]=${iface}
        eval "Network_Interface_${iface}_MAC_Address=${mac}"
        eval "Network_Interface_${iface}_IPv4_Address=${ip4addr}"
        if [[ -n ${nicnames} ]]; then
            eval "Network_Interface_${iface}_NIC_Names=${nicnames}"
        fi

        # Update interface list with the nic's interface name
        ag_count=1
        for list in "${AggrInterfaceList[@]}"; do
            AggrInterfaceList[${ag_count}]=${list//${mac}/${iface}}
            ((ag_count++))
        done

        ((count++))
    done

    for line in $(dladm show-phys -p -olink,state); do
        fields=(${line//:/ })
        iface=${fields[0]}
        link_status=${fields[1]}
        eval "Network_Interface_${iface}_Link_Status=${link_status}"

    done

    up_count=1

    for line in $(/sbin/dladm show-aggr -x -p \
        -o link,address,state,port | sed -e 's/\\:/|/g'); do
        nicnames=
        fields=(${line//:/ })
        iface=${fields[0]}
        mac=${fields[1]}
        state=${fields[2]}
        port=${fields[3]}

        # if the show-aggr line contains a port, use this to update the
        # list of interfaces that are actually on the aggr, rather than
        # what's defined in the config file
        if [[ -n ${port} ]]; then
            # If a nic is in an aggregation, its MAC address in the output of
            # `dladm show-phys -m` (used above) will be the MAC of the first
            # port in the aggregation.  Correct that here, so sysinfo will
            # still show the underlying nic's MAC.
            normalize_mac $(echo ${mac} | sed 's/\|/:/g')
            eval "Network_Interface_${port}_MAC_Address=${normalized}"

            ag_count=1
            for aggr in "${Aggrs[@]}"; do
                if [[ "${aggr}" == "${iface}" ]]; then
                    eval "seen=\${seen_${aggr}}"
                    if [[ -z "${seen}" ]]; then
                        AggrInterfaceList[${ag_count}]=${port}
                        eval "seen_${aggr}=true"
                    else
                        AggrInterfaceList[${ag_count}]=${AggrInterfaceList[${ag_count}]},${port}
                    fi

                    break
                fi
                ((ag_count++))
            done

            continue
        fi

        ip4addr=$(ifconfig ${iface} 2>/dev/null \
            | grep "inet " | awk '{ print $2 }')

        nic_tag_count=0
        while [[ ${nic_tag_count} -lt ${NicTagCount} ]]; do
            tag_name=${NicTagNames[${nic_tag_count}]}
            tag_mac=${NicTagMacs[${nic_tag_count}]}
            tag_link=${NicTagLinks[${nic_tag_count}]}
            ((nic_tag_count++))

            if [[ "${tag_mac}" == "-" ]] && \
                [[ "${tag_link}" == "${iface}" ]]; then
                [[ -n ${nicnames} ]] && nicnames="${nicnames},"
                nicnames="${nicnames}${tag_name}"
                NicTags[${nic_tag_count}]="${tag_name}=${iface}"
            fi
        done

        NetworkInterfaces[${count}]=${iface}
        normalize_mac $(echo ${mac} | sed 's/\|/:/g')
        eval "Network_Interface_${iface}_MAC_Address=${normalized}"
        eval "Network_Interface_${iface}_IPv4_Address=${ip4addr}"
        if [[ -n ${nicnames} ]]; then
            eval "Network_Interface_${iface}_NIC_Names=${nicnames}"
        fi

        eval "Network_Interface_${iface}_Link_Status=${state}"
        UpAggrs[${up_count}]="${iface}"
        ((count++))
        ((up_count++))
    done

    #for iface in "${NetworkInterfaces[@]}"
    #do
    #  eval "mac=\${Network_Interface_${iface}_MAC_Address}"
    #  eval "ipv4=\${Network_Interface_${iface}_IPv4_Address}"
    #  eval "nicnames=\${Network_Interface_${iface}_NIC_Names}"
    #  echo "mac: ${mac} ---- ${ipv4} ---- ${nicnames}"
    #done
}

function get_smartos_vnics()
{
    count=1

    # ignore 'LINK SLOT ADDRESS INUSE CLIENT' line
    for line in $(/sbin/dladm show-vnic -z '' \
        | grep -v "^LINK " | awk '{ print $1,$2,$4,$6 }' | tr ' ' '='); do
        fields=(${line//=/ })
        iface=${fields[0]}
        over=${fields[1]}
        normalize_mac ${fields[2]}
        mac=${normalized}
        vlan=${fields[3]}
        link_status=$(/sbin/dladm show-link -p -o state ${iface})
        ip4addr=$(ifconfig ${iface} 2>/dev/null \
            | grep "inet " | awk '{ print $2 }')
        VirtualNetworkInterfaces[${count}]=${iface}
        eval "Virtual_Network_Interface_${iface}_MAC_Address=${mac}"
        eval "Virtual_Network_Interface_${iface}_IPv4_Address=${ip4addr}"
        eval "Virtual_Network_Interface_${iface}_Link_Status=${link_status}"
        eval "Virtual_Network_Interface_${iface}_VLAN=${vlan}"
        eval "Virtual_Network_Interface_${iface}_Host_Interface=${over}"
        ((count++))
    done
}

function get_overlay_nic_tags()
{
    if /usr/lib/sdc/net-boot-config --enabled; then
        for line in $(/usr/lib/sdc/net-boot-config grep '_overlay_nic_tags_provided='); do
            eval "CONFIG_${line}"
        done
    fi
}

# Default to Admin_NIC_Tag of "admin"
function get_admin_nic_tag()
{
    prop=$(net_conf_grep "^admin_tag=")
    fields=(${prop//=/ })
    eval "Admin_NIC_Tag=${fields[1]:-"admin"}"
}

function get_admin_ip()
{
    # In case get_admin_nic_tag hasn't been called yet.
    [[ -z "$Admin_NIC_Tag" ]] && get_admin_nic_tag

    # If we are still missing the "Admin_NIC_Tag" default to "admin"
    tag=${Admin_NIC_Tag:-"admin"}
    prop=$(net_conf_grep "^${tag}_ip=")
    fields=(${prop//=/ })
    eval "Admin_IP=${fields[1]}"
}

function get_nic_tags()
{
    NicTagCount=0
    NicTagList=
    for line in $(nictagadm list -d '=' -p -L 2>/dev/null); do
        fields=(${line//=/ })

        NicTagNames[${NicTagCount}]=${fields[0]}
        NicTagMacs[${NicTagCount}]=${fields[1]}
        NicTagLinks[${NicTagCount}]=${fields[2]}

        [[ -n $NicTagList ]] && NicTagList=${NicTagList},
        NicTagList=${NicTagList}${fields[0]}
        ((NicTagCount++))
    done
}

function get_aggr_params()
{
    ag=1
    c=1
    for aggr in "${Aggrs[@]}"; do
        list=${AggrInterfaceList[${ag}]}
        if [[ ! $list =~ ":" ]]; then
            if [[ -z "${CompleteAggrs}" ]]; then
                CompleteAggrs="${aggr}"
            else
                CompleteAggrs="${CompleteAggrs},${aggr}"
            fi
            CompleteAggrList[${c}]="Aggregation_${aggr}_Interfaces=${list}"

            lacp_val=$(net_conf_grep "^${aggr}_lacp_mode=" | tail -n 1 | cut -f2 -d'=')
            if [[ -z "${lacp_val}" ]]; then
                lacp_val="off"
            fi
            eval "Aggregation_${aggr}_LACP_mode=${lacp_val}"
            eval "Aggregation_${aggr}_Interfaces=${list}"
            eval "Aggregation_${aggr}_MACs=${AggrMacs[${aggr}]}"

            ((c++))
        fi
        ((ag++))
    done
}

function get_bootparams()
{
    WHICH_GREP="/usr/xpg4/bin/grep"
    ORIGIFS=$IFS

    IFS=`echo -en "\n\b"`

    count=1
    for line in $(/usr/bin/bootparams); do
        fields=(${line//=/ })
        if ! (echo "${fields[0]}" | ${WHICH_GREP} \
          -e "^tty" \
          -e "^atapi" \
          -e "^ata-dma" \
          -e "^keyboard-" \
          -e "^bios-boot-device" \
          -e "^lba-access-" \
          -e "^boot-ncpus" \
          -e "^boot-file" \
          -e "^whoami" \
          -e "^mfg-name" \
          -e "^impl-arch-name" \
          -e "-max-ncpus$" \
          >/dev/null); then
            Bootparams[${count}]=${line}
            ((count++))
        fi
    done

    IFS=$ORIGIFS
}

function get_psrinfo()
{
    count=1
    for line in $(/usr/sbin/psrinfo -r all); do
        fields=(${line//=/ })
        Psrinfo[${count}]=${line}
        ((count++))
    done
}

function get_boot_time()
{
    # This is all there is to it.
    Boot_Time=$(/usr/bin/kstat -p -m unix -n system_misc -s boot_time | cut -f2)
}

function get_dc_info()
{
    if [[ -f /.dcinfo ]]; then
        Datacenter_Name=$(source /.dcinfo; echo "${SDC_DATACENTER_NAME}")
        # Headnode ID is only valid if it's an integer.
        Headnode_ID=$(source /.dcinfo; echo "${SDC_DATACENTER_HEADNODE_ID}" \
            | grep "^[0-9]*$")
    fi
}

function get_sdc_version()
{
    if [[ -f /.smartdc_version ]]; then
        SMARTDC_VERSION=$(cat /.smartdc_version)
    else
        SMARTDC_VERSION=
    fi
}

function get_setup()
{
    if [[ ! -e /var/lib/setup.json ]]; then
        Setup_complete=false
    else
        Setup_complete=$(json -f /var/lib/setup.json complete)
    fi
}

function get_recovery_configs()
{
    # This gets set to indicate if we should emit a
    # 'Zpool_Recovery' property in the output. CNs without
    # encryption should never emit this property.
    has_recovery=

    # This is a bit ugly, but we depend on get_zpool() being
    # called prior to get_recovery_configs() so that ${Zpool} is set.
    # The rest of the script already treats ${Zpool} as a global variable
    # set by get_zpool(), so we're not introducing any new ugliness.
    [[ -z "$Zpool" ]] && return

    for line in $(kbmadm recovery list -p "$Zpool" 2>/dev/null); do
        fields=(${line//:/ })
        cfg=${fields[0]}
        cfguuid=${fields[1]}
        if [[ -n "$cfguuid" ]]; then
            printf -v "Zpool_Recovery_${cfg}" "%s" "$cfguuid"
            has_recovery=1
        fi
    done
}

function output_parsable()
{
    cat <<END
Live_Image='${Live_Image}'
System_Type='${Uname_System}'
Boot_Time='${Boot_Time}'
END

    if [[ -n ${Datacenter_Name} ]]; then
    cat <<END
Datacenter_Name='${Datacenter_Name}'
END
    fi

    if [[ -n ${Headnode_ID} ]]; then
    cat <<END
Headnode_ID=${Headnode_ID}
END
    fi

    if [[ ${ZONENAME} == "global" ]]; then

        if [[ -n ${SMARTDC_VERSION} ]]; then
            echo "SDC_Version='${SMARTDC_VERSION}'"
        fi

        cat <<END
Manufacturer='${Manufacturer}'
Product='${Product}'
Serial_Number='${Serial_Number}'
SKU_Number='${SKU_Number}'
HW_Version='${HW_Version}'
HW_Family='${HW_Family}'
VM_Capable='${VM_Capable}'
CPU_Type='${CPU_Version}'
CPU_Virtualization='${CPU_Virtualization}'
CPU_Physical_Cores=${CPU_Socket_Count}
Bhyve_Capable='${Bhyve_Capable}'
Bhyve_Max_Vcpus=${Bhyve_Max_Vcpus}
HVM_API='${HVM_API}'
Nic_Tags=${NicTagList}
Admin_NIC_Tag=${Admin_NIC_Tag}
Admin_IP=${Admin_IP}
Setup='${Setup_complete}'
END
    else
        echo "Zfs_Quota='${QUOTA}'"
    fi

    cat <<END
UUID='${UUID}'
Hostname='${Hostname}'
CPU_Total_Cores=${CPU_Count}
CPU_Socket_Count=${CPU_Socket_Count}
CPU_Core_Count=${CPU_Core_Count}
CPU_Online_Count=${CPU_Online_Count}
CPU_Count=${CPU_Count}
MiB_of_Memory=${Memory_in_MiB}
END

    for entry in "${Disks[@]}"; do
        fields=(${entry//=/ })
        disk=${fields[0]}
        size=${fields[1]}
        gb_size=$((${size} / 1000000000))
        echo "Disk_${disk}_size_in_GB=${gb_size}"
    done

    for tag in "${NicTags[@]}"; do
        tag_fields=(${tag//=/ })
        tag_name=${tag_fields[0]}
        int=${tag_fields[1]}
        echo "NIC_${tag_name}='${int}'"
    done

    for iface in "${NetworkInterfaces[@]}"; do
        mac_var="Network_Interface_${iface}_MAC_Address"
        ipv4_var="Network_Interface_${iface}_IPv4_Address"
        nicnames_var="Network_Interface_${iface}_NIC_Names"
        link_status_var="Network_Interface_${iface}_Link_Status"
        detected_tag_var="Network_Interface_${iface}_Detected_Nic_Tag"

        eval "mac=\${${mac_var}}"
        eval "ipv4=\${${ipv4_var}}"
        eval "nicnames=\${${nicnames_var}}"
        link_status="unknown"
        eval "link_status=\${${link_status_var}}"
        eval "detected_tag=\${${detected_tag_var}}"

        echo "${mac_var}='${mac}'"
        echo "${ipv4_var}='${ipv4}'"
        echo "${nicnames_var}='${nicnames}'"
        echo "${link_status_var}='${link_status}'"
        if [[ -n "${detected_tag}" ]] ; then
            echo "${detected_tag_var}='${detected_tag}'"
        fi
    done

    for iface in "${VirtualNetworkInterfaces[@]}"; do
        mac_var="Virtual_Network_Interface_${iface}_MAC_Address"
        ipv4_var="Virtual_Network_Interface_${iface}_IPv4_Address"
        link_status_var="Virtual_Network_Interface_${iface}_Link_Status"
        vlan_var="Virtual_Network_Interface_${iface}_VLAN"
        host_var="Virtual_Network_Interface_${iface}_Host_Interface"

        eval "mac=\${${mac_var}}"
        eval "ipv4=\${${ipv4_var}}"
        eval "link_status=\${${link_status_var}}"
        eval "vlan=\${${vlan_var}}"
        eval "host=\${${host_var}}"

        echo "${mac_var}='${mac}'"
        echo "${ipv4_var}='${ipv4}'"
        echo "${link_status_var}='${link_status}'"
        echo "${vlan_var}='${vlan}'"
        if [[ ${ZONENAME} == "global" ]]; then
            echo "${host_var}='${host}'"
        fi
    done

    for entry in "${Bootparams[@]}"; do
        fields=(${entry//=/ })
        key=$(echo ${fields[0]} | sed -e 's+[^A-Za-z0-9_]+_+g')
        val=${fields[1]}
        echo "Bootparam_${key}='${val}'"
    done

    for entry in "${Psrinfo[@]}"; do
        fields=(${entry//=/ })
        key=$(echo ${fields[0]} | sed -e 's+[^A-Za-z0-9_]+_+g')
        val=${fields[1]}
        echo "Psrinfo_${key}='${val}'"
    done

    if [[ -n "$CompleteAggrs" ]]; then
        echo "Aggregations=$CompleteAggrs"
        for aggr in $(echo "${CompleteAggrs}" | sed -e "s/,/ /g"); do
            lacp_var="Aggregation_${aggr}_LACP_mode"
            eval "lacp=\${${lacp_var}}"
            echo "${lacp_var}=${lacp}"

            echo "Aggregation_${aggr}_MACs=${AggrMacs[${aggr}]}"
        done
    fi

    for aggr in "${CompleteAggrList[@]}"; do
        echo $aggr
    done
}

function output_json()
{
    cat <<END
{
  "Live Image": "${Live_Image}",
  "System Type": "${Uname_System}",
  "Boot Time": "${Boot_Time}",
END

    if [[ -n ${Datacenter_Name} ]]; then
    cat <<END
  "Datacenter Name": "${Datacenter_Name}",
END
    fi

    if [[ -n ${Headnode_ID} ]]; then
    cat <<END
  "Headnode ID": ${Headnode_ID},
END
    fi

    if [[ ${ZONENAME} == "global" ]]; then
        if [[ -n ${SMARTDC_VERSION} ]]; then
            echo "  \"SDC Version\": \"${SMARTDC_VERSION}\","
        fi

        cat <<END
  "Manufacturer": "${Manufacturer}",
  "Product": "${Product}",
  "Serial Number": "${Serial_Number}",
  "SKU Number": "${SKU_Number}",
  "HW Version": "${HW_Version}",
  "HW Family": "${HW_Family}",
  "Setup": "${Setup_complete}",
  "VM Capable": ${VM_Capable},
  "Bhyve Capable": ${Bhyve_Capable},
  "Bhyve Max Vcpus": ${Bhyve_Max_Vcpus},
  "HVM API": ${HVM_API},
  "CPU Type": "${CPU_Version}",
  "CPU Virtualization": "${CPU_Virtualization}",
  "CPU Physical Cores": ${CPU_Socket_Count},
  "Admin NIC Tag": "${Admin_NIC_Tag}",
  "Admin IP": "${Admin_IP}",
END
    else
        echo "  \"ZFS Quota\": \"${QUOTA}\","
    fi

cat <<END
  "UUID": "${UUID}",
  "Hostname": "${Hostname}",
  "CPU Total Cores": ${CPU_Count},
  "CPU Socket Count": ${CPU_Socket_Count},
  "CPU Core Count": ${CPU_Core_Count},
  "CPU Online Count": ${CPU_Online_Count},
  "CPU Count": ${CPU_Count},
  "MiB of Memory": "${Memory_in_MiB}",
END

if [[ ${ZONENAME} == "global" ]]; then
    if [[ -n ${Zpool} ]]; then
        echo '  "Zpool": "'${Zpool}'",'
        echo '  "Zpool Encrypted": '${Zpool_encrypted}','
        echo '  "Zpool Disks": "'${Zpool_disks}'",'
        echo '  "Zpool Profile": "'${Zpool_profile}'",'
        echo '  "Zpool Creation":' ${Zpool_creation}','
        echo '  "Zpool Size in GiB":' ${Zpool_size}','
    fi

    if [[ -n "has_recovery" ]]; then
        if [[ -n "$Zpool_Recovery_staged" ]]; then
            comma=","
        else
            comma=""
        fi

        echo '  "Zpool Recovery": {'
        if [[ -n "${Zpool_Recovery_active}" ]]; then
            echo '    "active": "'${Zpool_Recovery_active}'"'${comma}
        fi
        if [[ -n "${Zpool_Recovery_staged}" ]]; then
            echo '    "staged": "'${Zpool_Recovery_staged}'"'
        fi
        echo '  },'
    fi

    echo '  "Disks": {'

    printed=0
    for entry in "${Disks[@]}"; do
        fields=(${entry//=/ })
        disk=${fields[0]}
        size=${fields[1]}
        gb_size=$((${size} / 1000000000))
        ((printed++))
        trailing_comma=","
        [[ ${printed} -eq ${#Disks[*]} ]] && trailing_comma=''
        echo "    \"${disk}\": {\"Size in GB\": ${gb_size}}${trailing_comma}"
    done

    cat <<END
  },
  "Boot Parameters": {
END
    printed=0
    for entry in "${Bootparams[@]}"; do
        fields=(${entry//=/ })
        key=$(echo ${fields[0]} | sed -e 's+[^A-Za-z0-9_]+_+g')
        val=${fields[1]}
        ((printed++))
        trailing_comma=","
        [[ ${printed} -eq ${#Bootparams[*]} ]] && trailing_comma=''
        echo "    \"${key}\": \"$val\"${trailing_comma}"
    done
    cat <<END
  },
  "Psrinfo": {
END
    printed=0
    for entry in "${Psrinfo[@]}"; do
        fields=(${entry//=/ })
        key=$(echo ${fields[0]} | sed -e 's+[^A-Za-z0-9_]+_+g')
        val=${fields[1]}
        ((printed++))
        trailing_comma=","
        [[ ${printed} -eq ${#Psrinfo[*]} ]] && trailing_comma=''
        echo "    \"${key}\": \"$val\"${trailing_comma}"
    done
    cat <<END
  },
END

    if [[ ${ZONENAME} == "global" && -f /.smartdc_version \
        && -d /opt/smartdc/agents/lib/node_modules ]]; then

        # on SDC we also want to output the agents versions
        find /opt/smartdc/agents/lib/node_modules/ -maxdepth 2 \
            -name package.json -exec cat {} \; \
            | json -g -o json -a name version \
            | sed -e "s/^\[/\"SDC Agents\": \[/" \
            | sed -e "s/^/  /" | sed -e "s/\]$/\],/"
    fi

    cat <<END
  "Network Interfaces": {
END

    printed=0
    for iface in "${NetworkInterfaces[@]}"; do
        mac_var="Network_Interface_${iface}_MAC_Address"
        ipv4_var="Network_Interface_${iface}_IPv4_Address"
        nicnames_var="Network_Interface_${iface}_NIC_Names"
        link_status_var="Network_Interface_${iface}_Link_Status"
        detected_tag_var="Network_Interface_${iface}_Detected_Nic_Tag"
        nic_names_fmt=
        detected_fmt=

        eval "mac=\${${mac_var}}"
        eval "ipv4=\${${ipv4_var}}"
        eval "nicnames=\${${nicnames_var}}"
        eval "link_status=\${${link_status_var}}"
        eval "detected_tag=\${${detected_tag_var}}"

        if [[ -n "${detected_tag}" ]] ; then
            detected_fmt="\"Detected NIC Name\": \"${detected_tag}\", "
        fi

        nic_names_array=$(echo "${nicnames}" | sed -e "s/,/\", \"/g")
        if [[ -n ${nic_names_array} ]]; then
            nic_names_fmt="\"${nic_names_array}\""
        fi

        ((printed++))
        trailing_comma=","
        [[ ${printed} -eq ${#NetworkInterfaces[*]} ]] && trailing_comma=''
        echo -n "    \"${iface}\": {"
        echo -n "\"MAC Address\": \"${mac}\", "
        echo -n "\"ip4addr\": \"${ipv4}\", "
        echo -n "\"Link Status\": \"${link_status}\", "
        echo -n "${detected_fmt}"
        echo -n "\"NIC Names\": [${nic_names_fmt}]}"
        echo "${trailing_comma}"
    done

    echo "  },"
fi

cat << END
  "Virtual Network Interfaces": {
END
    printed=0
    for iface in "${VirtualNetworkInterfaces[@]}"; do
        mac_var="Virtual_Network_Interface_${iface}_MAC_Address"
        ipv4_var="Virtual_Network_Interface_${iface}_IPv4_Address"
        link_status_var="Virtual_Network_Interface_${iface}_Link_Status"
        vlan_var="Virtual_Network_Interface_${iface}_VLAN"
        host_var="Virtual_Network_Interface_${iface}_Host_Interface"

        eval "mac=\${${mac_var}}"
        eval "ipv4=\${${ipv4_var}}"
        eval "link_status=\${${link_status_var}}"
        eval "vlan=\${${vlan_var}}"
        eval "host=\${${host_var}}"
        eval "overlay_nic_tags=\${CONFIG_${iface}_overlay_nic_tags_provided}"

        ((printed++))
        trailing_comma=","
        [[ ${printed} -eq ${#VirtualNetworkInterfaces[*]} ]] && trailing_comma=''
        echo -n "    \"${iface}\": {"
        echo -n "\"MAC Address\": \"${mac}\", "
        echo -n "\"ip4addr\": \"${ipv4}\", "
        echo -n "\"Link Status\": \"${link_status}\", "
        if [[ ${ZONENAME} == "global" ]]; then
            echo -n "\"Host Interface\": \"${host}\", "
        fi
        if [[ -n $overlay_nic_tags ]]; then
            echo -n "\"Overlay Nic Tags\": [ \"${overlay_nic_tags//,/\", }\" ], "
        fi
        echo -n "\"VLAN\": \"${vlan}\"}"
        echo "${trailing_comma}"
    done

if [[ ${ZONENAME} == "global" ]]; then
    cat <<END
  },
  "Link Aggregations": {
END
    printed=0
    for aggr in "${UpAggrs[@]}"; do
        ((printed++))
        trailing_comma=","
        [[ ${printed} -eq ${#UpAggrs[*]} ]] && trailing_comma=''
        eval "lacp_mode=\${Aggregation_${aggr}_LACP_mode}"
        eval "links=\${Aggregation_${aggr}_Interfaces}"
        echo -n "    \"${aggr}\": {"
        echo -n "\"LACP mode\": \"${lacp_mode}\", "
        echo -n "\"Interfaces\": [\"$(echo ${links} | sed -e "s/,/\", \"/g")\"]"
        echo -n "}"
        echo "${trailing_comma}"
    done
fi

    cat <<END
  }
}
END
}

if [[ ${ZONENAME} == "global" ]]; then
    get_smbios_system_info
    get_vm_capable
    get_zpool
    get_disks
    get_recovery_configs
    get_bootparams
    get_psrinfo
    get_sdc_version
    if [[ -n ${SMARTDC_VERSION} ]]; then
        # Setup is SDC specific
        get_setup
    fi
else
    get_nonglobal_info
fi
get_memory_mib
get_smartos_cpu_info
get_bhyve_capability
get_live_image_buildstamp
get_system_type
get_hostname
get_aggregation_mappings
get_overlay_nic_tags
get_admin_nic_tag
get_admin_ip
get_nic_tags
get_smartos_network_interfaces
get_smartos_vnics
get_aggr_params
get_boot_time
get_dc_info

# whenever we update the cache, update both
if [[ ${CACHE} == "true" ]]; then
    output_parsable >> ${CACHE_FILE_PARSABLE}.new.$$ \
        && chmod 600 ${CACHE_FILE_PARSABLE}.new.$$ \
        && mv ${CACHE_FILE_PARSABLE}.new.$$ ${CACHE_FILE_PARSABLE}
    output_json >> ${CACHE_FILE_JSON}.new.$$ \
        && chmod 600 ${CACHE_FILE_JSON}.new.$$ \
        && mv ${CACHE_FILE_JSON}.new.$$ ${CACHE_FILE_JSON}
fi

# if we also want output, give it now
if [[ -z ${UPDATE_ONLY} ]]; then
    if [[ ${PARSABLE} == "true" ]]; then
        if [[ -z ${CACHE} ]]; then
            # We shouldn't read/write cache
            output_parsable
        else
            cat ${CACHE_FILE_PARSABLE} \
                || output_parsable
        fi
    else
        if [[ -z ${CACHE} ]]; then
            # We shouldn't read/write cache
            output_json
        else
            cat ${CACHE_FILE_JSON} \
                || output_json
        fi
    fi
fi

exit 0
